/*
  Install procedure :
	1. Copy the compiled dll file to dir : 
		C:\Program Files\ANSYS Inc\v140\ansys\lib\winx64\ 
	  or
		C:\Program Files (x86)\Ansys Inc\v100\ANSYS\lib\intel\
	2. Append the following two line to the end of "ANS_EXT.TBL" file:
		%ANSYS140_DIR%\Lib\winx64\AnsysExporter_win64.dll ~atof AnsysToFlac3D
		%ANSYS140_DIR%\Lib\winx64\AnsysExporter_win64.dll ~atoa AnsysToAbaqus
	  or
		C:\Program Files (x86)\Ansys Inc\v100\ANSYS\Lib\Intel\AnsysExporter_win32.dll ~atof AnsysToFlac3D
		C:\Program Files (x86)\Ansys Inc\v100\ANSYS\Lib\Intel\AnsysExporter_win32.dll ~atoa AnsysToAbaqus
	3. Launch ANSYS, issue the "~debug" command to check out if the extension dll is installed successfully.
	4. Enjoy!
*/

#include "stdafx.h"

#include "Win32Helper.h"

#define PCWINNT_SYS
#include <array>
#include <vector>
#include <map>
#include <list>
#include <iterator>
#include <algorithm>
#include <fstream>
#include <iostream>
#include <sstream>
#include <cctype>
#include <atltime.h>
#include <Shlwapi.h>
#include <atlconv.h>

#include "AnsysModel.h"

using Win32Helper::CDirectory;

extern "C"
{
#include <cAnsInterface.h>
#include <cAnsQuery.h>
};
#pragma comment(lib,"ansys.lib")

#pragma region Helper functions
//const char ENDLINE = '\n';

inline bool IsEqual(double v1,double v2)
{
	const double eps = 1.0e-6;
	double delta = v1-v2;
	return (-eps < delta) && (delta < eps);
}

template< typename T >
bool StrToNum(const std::string& str, T& num)
{
	std::istringstream iss(str);
	return !(iss>>num).fail();
}

void printString(LPCTSTR str)
{
	ATL::CW2A astr(str);
	cAns_printf((LPSTR)astr);
	cAns_printf("\n");
}

inline bool IsAlphabetic(CHAR c)
{
	return ('a' <= c && c <= 'z')
		|| ('A' <= c && c <= 'Z')
		|| ('0' <= c && c <= '9')
		|| (c == '~')
		|| (c == '-')
		|| (c == '_');
}

inline bool IsAlphabetic(WCHAR c)
{
	return ('a' <= c && c <= 'z')
		|| ('A' <= c && c <= 'Z')
		|| ('0' <= c && c <= '9')
		|| (c == '~')
		|| (c == '-')
		|| (c == '_');
}

ATL::CStringA TrimNonAlphabetic(const ATL::CStringA& str)
{
	if (str.GetLength()<1)
		return "";

	int start = 0;
	auto c = str[start];
	while (!IsAlphabetic(c))
	{
		++start;
		if (start>(str.GetLength()-1))
			break;

		c = str[start];
	}

	int end = str.GetLength()-1;
	c = str[end];
	while (!IsAlphabetic(c))
	{
		--end;
		if (end<0)
			break;

		c = str[end];
	}

	return ((start <= end) ? str.Mid(start, (end - start + 1)) : "");
}

ATL::CString TrimNonAlphabetic(const ATL::CString& str)
{
	if (str.GetLength() < 1)
		return L"";

	int start = 0;
	auto c = str[start];
	while (!IsAlphabetic(c))
	{
		++start;
		if (start > (str.GetLength() - 1))
			break;

		c = str[start];
	}

	int end = str.GetLength() - 1;
	c = str[end];
	while (!IsAlphabetic(c))
	{
		--end;
		if (end < 0)
			break;

		c = str[end];
	}

	return ((start <= end) ? str.Mid(start, (end - start + 1)) : L"");
}

void ToLower(char* str)
{
	for (int i = 0; str[i]!='\0'; i++)
	{
		str[i] = static_cast<char>(tolower(str[i]));
	}
}

std::string CStringToString(const ATL::CString& cstr)
{
	if (cstr.IsEmpty())
		return std::string("");

	ATL::CW2A acstr(cstr);
	std::string str((LPCSTR)acstr);
	return str;
}

ATL::CString StringToCString(const std::string& str)
{
	if (str.empty())
		return ATL::CString(L"");

	ATL::CString cstr(str.c_str());
	return cstr;
}

std::string GetAppVersionString()
{
	CTime buildDate = Win32Helper::CVersionInfo::GetBuildDate();
	char verStr[128] = { 0 };
	sprintf_s(verStr, "%4d.%02d.%02d",
		buildDate.GetYear(), buildDate.GetMonth(), buildDate.GetDay());
	return std::string(verStr);
}

std::string GetCurrentTimeString()
{
	CTime ct = CTime::GetCurrentTime();
	char timeStr[128] = { 0 };
	sprintf_s(timeStr, "%4d.%02d.%02d %02d:%02d:%02d", ct.GetYear(), ct.GetMonth(), ct.GetDay(),
		ct.GetHour(), ct.GetMinute(), ct.GetSecond());
	return std::string(timeStr);
}

void PrintString(const std::string& str)
{
	char cmdBuf[1024];
	int j = 0;
	for (size_t i = 0; i < str.size(); i++)
	{
		cmdBuf[j] = str[i];
		if (cmdBuf[j] == ENDLINE)
		{
			cmdBuf[j + 1] = '\0';
			::cAns_printf(cmdBuf);
			j = 0;
		}
		else
		{
			++j;
		}
	}
}

void PrintString(const char* str)
{
	PrintString(std::string{ str });
}

void PrintDebugString(const std::string& debugString)
{
#ifdef _DEBUG
	PrintString(debugString);
#endif // _DEBUG
}

void PrintAppVersionInfo()
{
	char verInfo[128] = { 0 };
	auto verStr = GetAppVersionString();
	sprintf_s(verInfo, "***** AnsysExporter v%s by wudong(wdhust@gmail.com) *****\n", verStr.c_str());
	char splitLine[] = "*****************************************************************\n";
	cAns_printf("\n\n");
	cAns_printf(splitLine);
	cAns_printf(verInfo);
	cAns_printf(splitLine);
	cAns_printf("\n\n");
};

void NormalizePathStr(std::string& pathStr)
{
	auto len = pathStr.size();
	if (pathStr[len-1] != '\\')
	{
		pathStr += '\\';
	}
}

std::string PathCanonicalize(const std::string& path)
{
	CHAR pathDest[MAX_PATH];
	::PathCanonicalizeA(pathDest, path.c_str());
	return std::string{ pathDest };
}

std::string PathCombine(const std::string& path1, const std::string& path2)
{
	CHAR pathDest[MAX_PATH];
	::PathCombineA(pathDest, path1.c_str(), path2.c_str());
	return std::string{ pathDest };
}

bool IsDirectoryExists(const std::string& dirPath)
{
	DWORD dwAttrib = GetFileAttributesA(dirPath.c_str());
	return (dwAttrib != INVALID_FILE_ATTRIBUTES &&
		(dwAttrib & FILE_ATTRIBUTE_DIRECTORY));
}

#pragma endregion

#ifdef _DEBUG
#define PrintCurrentLineInfo() cAns_printf(">>>>>>>> current line = %d\n", __LINE__);
#else
#define PrintCurrentLineInfo() {};
#endif

#pragma region AnsysToFlac3D
//////////////////////////////////////////////////////////////////////////
// Export to FLAC3D
//
extern "C" __declspec(dllexport)
int __cdecl AnsysToFlac3D0(char* uecmd)
{
	CTime buildDate = Win32Helper::CVersionInfo::GetBuildDate();
	char verInfo[128] = {0};
	sprintf_s(verInfo, "***** AnsysToFlac3D v%4d.%02d.%02d by wudong(wdhust@gmail.com) *****\n",
		buildDate.GetYear(),buildDate.GetMonth(),buildDate.GetDay());
	char splitLine[] = "******************************************************************\n";
	cAns_printf("\n\n");
	cAns_printf(splitLine);
	cAns_printf(verInfo);
	cAns_printf(splitLine);

	//
	ATL::CTime startTime = ATL::CTime::GetCurrentTime();

	// test command line mode
	cAns_printf("\nuecmd = \n");
	cAns_printf(uecmd);
	ATL::CA2W uecmdStr(uecmd);
	ATL::CString cmdLine = (LPCTSTR)uecmdStr;
	cmdLine = TrimNonAlphabetic(cmdLine);

	std::vector<ATL::CString> vecToken;

	int curPos = 0;
	ATL::CString token = cmdLine.Tokenize(L",",curPos);
	while (token!=L"")
	{
		token.Trim();
		vecToken.push_back(token);
		token = cmdLine.Tokenize(L",",curPos);
	}
	ATLASSERT(vecToken.size()>0 && vecToken[0]==L"~atof");

	cAns_printf("\nvecToken = \n");
	std::for_each(vecToken.begin(),vecToken.end(),[](ATL::CString& token)
	{
		printString(token);
	});

	std::wstring fileSavePath;
	bool transformCoord = false;
	if (vecToken.size()<1)
	{
		cAns_printf("Invalid command line : \n");
		cAns_printf(uecmd);
		return 0;
	}
	else if (vecToken.size()==1)
	{
		cAns_printf("Export ANSYS model to FLAC3D. Command Format : \n");
		cAns_printf("    ~atof, outputPath [,coordTrans] \n");
		cAns_printf("Sample : \n");
		cAns_printf("    ~atof, 'D:\\AnsysToFlac3d\\myf3grid.flac3d,', 1 \n");

		return 0;
	}
	else // vecToken.size()>=2
	{
		ATL::CString path = TrimNonAlphabetic(vecToken[1]);

		cAns_printf("path = ");
		printString(path);
		cAns_printf("\n");

		ATL::CString dir = path.Left(path.ReverseFind(L'\\')+1);
		cAns_printf("dir = ");
		printString(dir);
		cAns_printf("\n");
		if (!Win32Helper::CDirectory::IsDirectoryExist(dir))
		{
			cAns_printf("\nSave directory doesn't exist !\n");
			return 0;
		}

		fileSavePath = (LPCWSTR)path;

		if (vecToken.size()>2)
		{
			ATL::CString str;
			str.Format(L"vecToken[2] = %s", vecToken[2]);
			printString(str);
			int transCoordValue = _ttoi(vecToken[2]);
			cAns_printf("\ntransCoordValue = %d\n",transCoordValue);
			transformCoord = ( transCoordValue>0? true:false );
		}
	}

	printString(L"cmdName = ");
	printString(vecToken[0]);
	printString(L"fileSavePath = ");
	printString(fileSavePath.c_str());
	printString(L"transformCoordinate = ");
	printString(transformCoord==true? L"true":L"false");

	// Unselect all elements other than Solid45 and Solid95
	cAnsSendCommand("/prep7"										);
	cAnsSendCommand("/uis,msgpop,3"									);
	cAnsSendCommand("*get,etmax_,etyp,,num,max"						);
	cAnsSendCommand("*set,etnam_,0"									);
	cAnsSendCommand("*do,ii,1,etmax_"								);
	cAnsSendCommand("	*get,etnam_,etyp,ii,attr,enam"				);
	cAnsSendCommand("	*if,etnam_,gt,0,then"						);
	cAnsSendCommand("		*if,etnam_,ne,45,and,etnam_,ne,95,then"	);
	cAnsSendCommand("			esel,u,type,,ii"					);
	cAnsSendCommand("		*endif"									);
	cAnsSendCommand("	*endif"										);
	cAnsSendCommand("*enddo"										);
	cAnsSendCommand("etnam_ ="										);
	cAnsSendCommand("etmax_ ="										);

	// Get element data, store in ansys array
	cAnsSendCommand("*get,ec_,elem,,count"					);
	cAnsSendCommand("*dim,ed_solid_,array,ec_,9"			);
	cAnsSendCommand("*get,em_,elem,,num,min"				);
	cAnsSendCommand("*set,en_,em_"							);
	cAnsSendCommand("*set,matn_,0"							);
	cAnsSendCommand("*do,ii,1,ec_"							);
	cAnsSendCommand("	ed_solid_(ii,1) = nelem(en_,1)"		);
	cAnsSendCommand("	ed_solid_(ii,2) = nelem(en_,2)"		);
	cAnsSendCommand("	ed_solid_(ii,3) = nelem(en_,3)"		);
	cAnsSendCommand("	ed_solid_(ii,4) = nelem(en_,4)"		);
	cAnsSendCommand("	ed_solid_(ii,5) = nelem(en_,5)"		);
	cAnsSendCommand("	ed_solid_(ii,6) = nelem(en_,6)"		);
	cAnsSendCommand("	ed_solid_(ii,7) = nelem(en_,7)"		);
	cAnsSendCommand("	ed_solid_(ii,8) = nelem(en_,8)"		);
	cAnsSendCommand("	*get,matn_,elem,en_,attr,mat"		);
	cAnsSendCommand("	ed_solid_(ii,9) = matn_"			);
	cAnsSendCommand("	en_ = elnext(en_)"					);
	cAnsSendCommand("*enddo"								);
	cAnsSendCommand("matn_ ="								);
	cAnsSendCommand("en_ ="									);
	cAnsSendCommand("em_ ="									);

	// Get node data, store in ansys array
	cAnsSendCommand("nsel,none"							);
	cAnsSendCommand("nsle,s,corner"						);
	cAnsSendCommand("*get,nc_,node,,count"				);
	cAnsSendCommand("*dim,nd_,array,nc_,4"				);
	cAnsSendCommand("*get,nm_,node,,num,min"			);
	cAnsSendCommand("*set,nn_,nm_"						);
	cAnsSendCommand("*do,ii,1,nc_"						);
	cAnsSendCommand("	nd_(ii,1) = nn_"				);
	cAnsSendCommand("	nd_(ii,2) = nx(nn_)"			);	
	cAnsSendCommand("	nd_(ii,3) = ny(nn_)"			);
	cAnsSendCommand("	nd_(ii,4) = nz(nn_)"			);
	cAnsSendCommand("	nn_ = ndnext(nn_)"				);
	cAnsSendCommand("*enddo"							);
	cAnsSendCommand("nn_ ="								);
	cAnsSendCommand("nm_ ="								);

	// Get element count
	double retValue = 0.0;
	char strMsg[MAX_PATH];
	int iType  = 0;
	int res = cAnsEvalExpr("ec_",&retValue,strMsg,&iType);
	ATLASSERT(res==0 && iType==1);
	int elemCount = static_cast<int>(retValue);
	cAnsSendCommand("ec_ =");

	// Get node count
	res = cAnsEvalExpr("nc_",&retValue,strMsg,&iType);
	ATLASSERT(res==0 && iType==1);
	int nodeCount = static_cast<int>(retValue);
	cAnsSendCommand("nc_ =");

	// Get element data
	int dataSize = elemCount*9;
	double* pDataBuf = new double[dataSize];
	ATLASSERT(pDataBuf);
	cAnsGetVect(pDataBuf,"ed_solid_");
	int elemDataSize = elemCount*8;
	std::vector<int> elemData(elemDataSize);
	std::transform(
		pDataBuf,
		pDataBuf+elemDataSize,
		elemData.begin(),
		[](double v) { 
			return static_cast<int>(v);
		}
	);
	std::vector<int> groupData(elemCount);
	std::transform(
		pDataBuf+elemDataSize,
		pDataBuf+dataSize,
		groupData.begin(),
		[](double v) { 
			return static_cast<int>(v);
		}
	);
	delete[] pDataBuf;
	pDataBuf = nullptr;
	cAnsSendCommand("*del,ed_solid_,,nopr");

	// Get node data
	dataSize = nodeCount*4;
	pDataBuf = new double[dataSize];
	ATLASSERT(pDataBuf);
	cAnsGetVect(pDataBuf,"nd_");
	std::vector<double> nodeData(pDataBuf,pDataBuf+dataSize);
	delete[] pDataBuf;
	pDataBuf = nullptr;
	cAnsSendCommand("*del,nd_,,nopr");
	cAnsSendCommand("/uis,msgpop,2");

	// Compress node number
	/*
	for (int i=0;i<nodeCount;++i)
	{
		std::replace_if(
			elemData.begin(),
			elemData.end(),
			[i,&nodeData](int v) { 
				return v == static_cast<int>(nodeData[i]); 
			},
			(i+1)
		);
	}
	*/

	// Output grid data 
	std::ofstream ofs;
	ofs.open(fileSavePath,std::ios_base::out|std::ios_base::trunc);
	ATLASSERT(!ofs.bad());

	//const char ENDLINE[] = "\n";
	ofs << "* FLAC3D grid produced by FLAC3D" << ENDLINE;

	// Output gridpoint data
	ofs << "* GRIDPOINTS" << ENDLINE;
	
	std::array<int,3> nodeBase;
	for (size_t i=0;i<nodeBase.size();++i)
	{
		nodeBase[i] = static_cast<int>(nodeCount*(i+1));
	}
	for (int i=0;i<nodeCount;++i)
	{
		if (transformCoord)						// transform coordinate from XYZ to ZXY
		{
			ofs << "G"   << " " 
				<< static_cast<int>(nodeData[i]) << " "	// gp number
				<<  nodeData[nodeBase[0]+i] << " "		// X
				<< -nodeData[nodeBase[2]+i] << " "		// Y
				<<  nodeData[nodeBase[1]+i] << ENDLINE;	// Z
		}
		else
		{
			ofs << "G"   << " " 
				<< static_cast<int>(nodeData[i]) << " "	// gp number
				<< nodeData[nodeBase[0]+i] << " "		// X
				<< nodeData[nodeBase[1]+i] << " "		// Y
				<< nodeData[nodeBase[2]+i] << ENDLINE;	// Z
		}
	}

	// Output zone data
	ofs << "* ZONES" << ENDLINE;

	std::map<int,std::list<int>> group;
	std::array<int,8> elemBase;
	for (size_t i=0;i<elemBase.size();++i)
	{
		elemBase[i] = static_cast<int>(i*elemCount);
	}

	for (int row=0;row<elemCount;++row)
	{
		int en = row+1;

		int gn = groupData[row];
		ATLASSERT(gn>0);
		group[gn].push_back(en);

		int ni = elemData[elemBase[0]+row];
		int nj = elemData[elemBase[1]+row];
		int nk = elemData[elemBase[2]+row];
		int nl = elemData[elemBase[3]+row];
		int nm = elemData[elemBase[4]+row];
		int nn = elemData[elemBase[5]+row];
		int no = elemData[elemBase[6]+row];
		int np = elemData[elemBase[7]+row];
		ATLASSERT(ni!=nj && nj!=nk && nk!=ni);
		if (nm==nn && nn==no && no==np)
		{
			if (nk==nl)		// Tetrahedral (T4)
			{
				int p0=ni, p1=nj, p2=nk, p3=nm;
				ofs << "Z"  << " "
					<< "T4" << " "
					<< en   << " "
					<< p0 << " "
					<< p1 << " "
					<< p2 << " "
					<< p3 << ENDLINE;
			}
			else			// Pyramid (P5)
			{
				int p0=ni, p1=nj, p2=nl, p3=nm, p4=nk;
				ofs << "Z"  << " "
					<< "P5" << " "
					<< en   << " "
					<< p0 << " "
					<< p1 << " "
					<< p2 << " "
					<< p3 << " "
					<< p4 << ENDLINE;
			}
		}
		else if (nk==nl)	// Prism(wedge) (W6)
		{
			ATLASSERT(no==np);
			int p0=ni, p1=nk, p2=nm, p3=nj, p4=no, p5=nn;
			ofs << "Z"  << " "
				<< "W6" << " "
				<< en   << " "
				<< p0 << " "
				<< p1 << " "
				<< p2 << " "
				<< p3 << " "
				<< p4 << " "
				<< p5 << ENDLINE;
		}
		else				// Brick (B8)
		{
			int p0=ni, p1=nj, p2=nl, p3=nm;
			int p4=nk, p5=np, p6=nn, p7=no;
			ofs << "Z"  << " "
				<< "B8" << " "
				<< en   << " "
				<< p0 << " "
				<< p1 << " "
				<< p2 << " "
				<< p3 << " "
				<< p4 << " "
				<< p5 << " "
				<< p6 << " "
				<< p7 << ENDLINE;
		}
	}

	cAns_printf("\n\nOutput group data ......\n");

	// Exprot group info
	ofs << "* GROUPS" << ENDLINE;
	for (auto g=group.cbegin();g!=group.cend();++g)
	{
		const auto& elist = g->second;
		if (elist.size()<=0)	// skip empty group
			continue;

		cAns_printf("group%d, elem count = %d\n",g->first,elist.size());

		ofs << "ZGROUP" << " " << "group" << g->first << ENDLINE;

		int count = 0;
		const int COUNT_PER_LINE = 10;
		for (auto e=elist.cbegin();e!=elist.cend();++e)
		{
			ofs << " " << (*e);
			++count;
			if ((count%COUNT_PER_LINE) == 0)
				ofs << ENDLINE;
		}
		ofs << ENDLINE;
	}

	ofs.close();

	//
	ATL::CTime endTime = ATL::CTime::GetCurrentTime();
	ATL::CTimeSpan dt = endTime - startTime;

	cAns_printf("All selected elements have been converted.\n");
	ATL::CW2A filePath(fileSavePath.c_str());
	cAns_printf("Output path : %s\n",(LPSTR)filePath);
	cAns_printf("Node count = %d\n",nodeCount);
	cAns_printf("Elem count = %d\n",elemCount);
	cAns_printf("Conversion time = %02d:%02d:%02d\n",
		dt.GetHours(),dt.GetMinutes(),dt.GetSeconds());

	cAns_printf("\n");
	cAns_printf(splitLine);
	cAns_printf(verInfo);
	cAns_printf(splitLine);
	cAns_printf("\n\n");

	return 0;
}

class CAtofCmdInfo
{
public:
	CAtofCmdInfo(char* uecmd)
	{
		//
		// Tokenize command line
		//
		std::string cmdLine{ uecmd };
		auto argCount = std::count_if(cmdLine.cbegin(), cmdLine.cend(), [](char c) { return c == ','; });
		if (argCount <= 0)
		{
			this->HasArgument = false;
			return;
		}

		if (argCount<ArgCount)
		{
			cmdLine += std::string((ArgCount - argCount), ',');
		}

		auto tokens = CStringHelper::SplitString(cmdLine, ',');
		if (tokens.size() <= 1)
		{
			return;
		}
		std::vector<std::string> arguments;
		for (size_t i = 1; i < tokens.size(); i++)
		{
			arguments.push_back(CStringHelper::Trim(tokens[i], std::string(" \t\"\'")));
		}

		//
		// Get output file name
		//
		this->OutputFileName = arguments[0];

		//
		// Get current work directory
		//
		CAnsysHelper::RunAnsysCommand("/inquire,workdir0_,directory");
		this->CurWorkDir = CPathHelper::PathNormalize(CAnsysHelper::GetStringVar("workdir0_(1)"));

		//
		// Get main output directory
		//
		this->OutputMainDir = this->CurWorkDir;
		std::string outputMainDir{ arguments[1] };
		std::transform(arguments[1].cbegin(), arguments[1].cend(), outputMainDir.begin(), [](char c) { return c == '/' ? '\\' : c; });
		outputMainDir = CStringHelper::Trim(outputMainDir, std::string{ " \t\\" });
		if (outputMainDir.length()>0)
		{
			if (::PathIsRelativeA(outputMainDir.c_str()))
			{
				this->OutputMainDir = CPathHelper::PathNormalize(CPathHelper::PathCombine(this->CurWorkDir, outputMainDir));
			}
			else
			{
				this->OutputMainDir = CPathHelper::PathNormalize(outputMainDir);
			}
			if (CDirectoryHelper::IsDirectoryExists(this->OutputMainDir))
			{
				CDirectoryHelper::CreateDirectory(this->OutputMainDir);
			}
		}
	}

private:
	CAtofCmdInfo() {}

public:
	bool HasArgument = true;
	std::string OutputFileName;
	std::string CurWorkDir;
	std::string OutputMainDir;
	const int ArgCount{ 2 };
};

extern "C" __declspec(dllexport)
int __cdecl AnsysToFlac3D(char* uecmd)
{
	//////////////////////////////////////////////////////////////////////////
	//
	auto startTime = ATL::CTime::GetCurrentTime();

	//////////////////////////////////////////////////////////////////////////
	// Parse the command line
	//
	PrintAppVersionInfo();

	CAtofCmdInfo ci(uecmd);
	if (!ci.HasArgument)
	{
		CAnsysHelper::PrintString(R"(
Export ANSYS model to FLAC3D 5.0. 
Supported element type:
	Solid45

Command Format :
	~atof,fileName [,outputPath]
Sample :
	~atof,'myFlac3dModel.flac3d','D:\AnsysToFlac3d\OutputDir\'
		)");
		PrintAppVersionInfo();

		return 0;
	}

	std::map<int, std::tuple<std::string, bool>> elemConvertionMap
	{
		std::make_pair(45, std::make_tuple(std::string("B8"),  false)),
	};
	std::vector<int> exportedElemTypeNums
	{
		45,
	};

	CAnsysMeshModel amm;
	amm.RetriveAnsysInternalMeshData(exportedElemTypeNums);

	const auto& nodeData = amm.GetNodeData();
	const auto& elemData = amm.GetElemData();
	const auto& groupData = amm.GetGroupData();
	const auto& internalElemTypes = amm.InternalSupportedElemTypes;

	//
	// Open inp file for write
	//
	std::ofstream ofs;
	std::string mainFilePath{ CPathHelper::PathCombine(ci.OutputMainDir, ci.OutputFileName) };
	CAnsysHelper::PrintDebugString("mainFilePath = %s", mainFilePath.c_str());
	const auto FileOpenMode = std::ios_base::out | std::ios_base::trunc;
	ofs.open(mainFilePath, FileOpenMode);

	auto verStr = GetAppVersionString();
	auto curTimeStr = GetCurrentTimeString();
	ofs << "* Flac3D mesh file generated by AnsysExporter v" << verStr << " (Author: wudong <wdhust@gmail.com>)" << ENDLINE
		<< "*     at <" << curTimeStr << ">" << ENDLINE;

	PrintCurrentLineInfo();

	//
	// Export node data
	//
	auto nodeCount = nodeData.size();
	if (nodeCount > 0)
	{
		ofs << "* Gridpoints" << ENDLINE;
		for (auto&& node : nodeData)		// for each node
		{
			auto nodeNum = node.first;
			auto nodeCoord = node.second;
			auto x = std::get<0>(nodeCoord);
			auto y = std::get<1>(nodeCoord);
			auto z = std::get<2>(nodeCoord);
			ofs << "G " << nodeNum << "," << x << "," << y << "," << z << ENDLINE;	// "G nodeNum,x,y,z"
		}
	}

	PrintCurrentLineInfo();

	//
	// Export element data
	//
	size_t elemCountAll = 0;
	if (!elemData.empty())
	{
		assert(elemData.size() == 1 && (*elemData.cbegin()).first == 45);
		elemCountAll = (*elemData.cbegin()).second.size();

		ofs << "* Zones" << ENDLINE;
		auto elems = (*elemData.cbegin()).second;
		for(auto&& elem : elems)
		{
			assert(elem.size() >= 9);
			int en = elem[0];
			int n1 = elem[1];
			int n2 = elem[2];
			int n3 = elem[3];
			int n4 = elem[4];
			int n5 = elem[5];
			int n6 = elem[5];
			int n7 = elem[7];
			int n8 = elem[8];
			assert(n1 != n2 && n2 != n3 && n3 != n1);
			if (n5 == n6 && n6 == n7 && n7 == n8)
			{
				if (n3 == n4)		// Tetrahedral (T4)
				{
					int p1 = n1, p2 = n2, p3 = n3, p4 = n5;
					ofs << "Z" << " "
						<< "T4" << " "
						<< en << " "
						<< p1 << " "
						<< p2 << " "
						<< p3 << " "
						<< p4 << ENDLINE;
				}
				else			// Pyramid (P5)
				{
					int p1 = n1, p2 = n2, p3 = n4, p4 = n5, p5 = n3;
					ofs << "Z" << " "
						<< "P5" << " "
						<< en << " "
						<< p1 << " "
						<< p2 << " "
						<< p3 << " "
						<< p4 << " "
						<< p5 << ENDLINE;
				}
			}
			else if (n3 == n4)	// Prism(wedge) (W6)
			{
				assert(n7 == n8);
				int p1 = n1, p2 = n3, p3 = n5, p4 = n2, p5 = n7, p6 = n6;
				ofs << "Z" << " "
					<< "W6" << " "
					<< en << " "
					<< p1 << " "
					<< p2 << " "
					<< p3 << " "
					<< p4 << " "
					<< p5 << " "
					<< p6 << ENDLINE;
			}
			else				// Brick (B8)
			{
				int p1 = n1, p2 = n2, p3 = n4, p4 = n5, p5 = n3, p6 = n8, p7 = n6, p8 = n7;
				ofs << "Z" << " "
					<< "B8" << " "
					<< en << " "
					<< p1 << " "
					<< p2 << " "
					<< p3 << " "
					<< p4 << " "
					<< p5 << " "
					<< p6 << " "
					<< p7 << " "
					<< p8 << ENDLINE;
			}
		}
	}

	PrintCurrentLineInfo();

	//
	// Export group data
	//
	std::map<std::string, int> exportGroups;
	if (!groupData.empty())
	{
		ofs << "* Zone Groups" << ENDLINE;
		for (auto&& group : groupData)		// for each group
		{
			auto groupName = CStringHelper::ToUpper(group.first);
			ofs << "ZGROUP \'" << groupName << "\'" << ENDLINE;

			CAnsysHelper::PrintDebugString("Start exporting group : %s", groupName.c_str());

			auto groupType = std::get<0>(group.second);
			auto& groupMembers = std::get<1>(group.second);
			if (groupType == GroupType::Element)
			{
				for (size_t i = 0; i < groupMembers.size(); i++)
				{
					const int NumCountEachLine = 15;
					ofs << groupMembers[i] << (((i + 1) % NumCountEachLine != 0) ? ',' : ENDLINE);
				}
			}
			exportGroups[groupName] = groupMembers.size();

			//
			CAnsysHelper::PrintDebugString("End exporting group : %s", groupName.c_str());
		}
	}

	//
	ofs << "* End" << ENDLINE;
	ofs.close();

	CAnsysHelper::PrintDebugString("End exporting actions");

	//////////////////////////////////////////////////////////////////////////
	// Print output result
	//
	auto endTime = ATL::CTime::GetCurrentTime();
	auto dt = endTime - startTime;

	std::stringstream ss;
	ss << ENDLINE << ENDLINE
		<< "****************** Output result ******************" << ENDLINE
		<< "All selected elements have been converted." << ENDLINE
		<< "Working directory : " << ci.CurWorkDir << ENDLINE
		<< "Output directory  : " << mainFilePath << ENDLINE
		<< "Node count : " << nodeCount << ENDLINE
		<< "Elem count : " << elemCountAll << ENDLINE;

	CAnsysHelper::PrintDebugString("Print elem and group info");

	CAnsysHelper::PrintString("%s", ss.str().c_str());
	CAnsysHelper::PrintString("Conversion time = %02d:%02d:%02d\n", dt.GetHours(), dt.GetMinutes(), dt.GetSeconds());

	//
	PrintAppVersionInfo();

	return 0;
}
#pragma endregion

#pragma region AnsysToAbaqus
//////////////////////////////////////////////////////////////////////////
// Export to ABAQUS
//
class CAtoaCmdInfo
{
public:
	CAtoaCmdInfo(char* uecmd)
		: InpFileName("ansModel")
	{
		std::string cmdLine{ uecmd };
		auto argCount = std::count_if(cmdLine.cbegin(), cmdLine.cend(), [](char c) { return c == ','; });
		if (argCount<=0)
		{
			this->HasArgument = false;
			return;
		}

		if (argCount<ArgCount)
		{
			cmdLine += std::string((ArgCount - argCount), ',');
		}

		auto tokens = CStringHelper::SplitString(cmdLine, ',');
		if (tokens.size()<=1)
		{
			return;
		}
		std::vector<std::string> arguments;
		for (size_t i = 1; i < tokens.size(); i++)
		{
			arguments.push_back(CStringHelper::Trim(tokens[i], std::string(" \t\"\'")));
		}

		auto fileName = arguments[0];
		auto fileNameLength = fileName.length();
		if (fileNameLength>0)
		{
			auto fileExtStr = "." + this->InpFileExt;
			auto fileExtLength = fileExtStr.length();
			//
			// If fileName is end with ".inp", extract the short file name
			//
			if (fileName.substr(fileNameLength- fileExtLength, fileExtLength) == fileExtStr)
			{
				this->InpFileName = fileName.substr(0, fileNameLength - fileExtLength);
			}
			else
			{
				this->InpFileName = fileName;
			}
		}

		//
		// Get current work directory
		//
		CAnsysHelper::RunAnsysCommand("/inquire,workdir0_,directory");
		this->CurWorkDir = CPathHelper::PathNormalize(CAnsysHelper::GetStringVar("workdir0_(1)"));

		//
		// Get main output directory
		//
		this->OutputMainDir = this->CurWorkDir;
		std::string outputMainDir{ arguments[1] };
		std::transform(arguments[1].cbegin(), arguments[1].cend(), outputMainDir.begin(), [](char c) { return c == '/' ? '\\' : c; });
		outputMainDir = CStringHelper::Trim(outputMainDir, std::string{ " \t\\" });
		if (outputMainDir.length()>0)
		{
			if (::PathIsRelativeA(outputMainDir.c_str()))
			{
				this->OutputMainDir = CPathHelper::PathNormalize(CPathHelper::PathCombine(this->CurWorkDir, outputMainDir));
			}
			else
			{
				this->OutputMainDir = CPathHelper::PathNormalize(outputMainDir);
			}
			if (CDirectoryHelper::IsDirectoryExists(this->OutputMainDir))
			{
				CDirectoryHelper::CreateDirectory(this->OutputMainDir);
			}
		}

		//
		// Set set data output directory
		//
		this->OutputSetDataDirName = this->InpFileName + "_data";
		auto setDataDir = CPathHelper::PathCombine(this->OutputMainDir, this->OutputSetDataDirName);
		if (!CDirectoryHelper::IsDirectoryExists(setDataDir))
		{
			CDirectoryHelper::CreateDirectory(setDataDir);
		}
	}

private:
	CAtoaCmdInfo() {}

public:
	bool HasArgument = true;
	std::string InpFileName;
	std::string CurWorkDir;
	std::string OutputMainDir;
	std::string OutputSetDataDirName;

	const std::string CmdName{ "~atoa" };
	const std::string InpFileExt{ "inp" };
	const int ArgCount{ 2 };
};

//
// The main entrance function
//
extern "C" __declspec(dllexport)
int __cdecl AnsysToAbaqus(char* uecmd)
{
	//////////////////////////////////////////////////////////////////////////
	//
	auto startTime = ATL::CTime::GetCurrentTime();

	//////////////////////////////////////////////////////////////////////////
	// Parse the command line
	//
	PrintAppVersionInfo();

	CAtoaCmdInfo ci(uecmd);
	if (!ci.HasArgument)
	{
		CAnsysHelper::PrintString(R"(
Export ANSYS model to ABAQUS. 
Supported element type:
	2D :
	Plane42  -> CPE4R
	Plane182 -> CINPE4
	Beam3    -> B21
	Link1    -> T2D2

	3D :
	Solid45  -> C3D8R
	Shell43  -> S4R
	Shell41  -> M3D4R
	Beam4    -> B31
	Link8    -> T3D2

Command Format :
	~atoa,fileName [,outputPath]
Sample :
	~atoa,'myAbaqusModel.inp','D:\AnsysToAbaqus\OutputDir\'
		)");
		PrintAppVersionInfo();

		return 0;
	}

	std::map<int, std::tuple<std::string, bool>> elemConvertionMap
	{
		// 2D element
		std::make_pair(42, std::make_tuple(std::string("CPE4R"),  false)),
		std::make_pair(182, std::make_tuple(std::string("CINPE4"), true)),	// 2D infinite element
		std::make_pair(3, std::make_tuple(std::string("B21"),    false)),
		std::make_pair(1, std::make_tuple(std::string("T2D2"),   false)),

		// 3D element
		std::make_pair(45, std::make_tuple(std::string("C3D8R"),  false)),
		std::make_pair(43, std::make_tuple(std::string("S4R"),    false)),
		std::make_pair(41, std::make_tuple(std::string("M3D4R"),  false)),
		std::make_pair(4, std::make_tuple(std::string("B31"),    false)),
		std::make_pair(8, std::make_tuple(std::string("T3D2"),   false))
	};
	std::vector<int> exportedElemTypeNums
	{
		42, 182, 3, 1,
		45, 43, 41, 4, 8,
	};

	CAnsysMeshModel amm;
	amm.RetriveAnsysInternalMeshData(exportedElemTypeNums);

	const auto& nodeData = amm.GetNodeData();
	const auto& elemData = amm.GetElemData();
	const auto& groupData = amm.GetGroupData();
	const auto& internalElemTypes = amm.InternalSupportedElemTypes;

	//
	// Open inp file for write
	//
	std::ofstream ofsMainInp;
	std::string mainFilePath{ CPathHelper::PathCombine(ci.OutputMainDir, ci.InpFileName + "." + ci.InpFileExt) };
	CAnsysHelper::PrintDebugString("mainFilePath = %s", mainFilePath.c_str());
	const auto FileOpenMode = std::ios_base::out | std::ios_base::trunc;
	ofsMainInp.open(mainFilePath, FileOpenMode);

	auto verStr = GetAppVersionString();
	auto curTimeStr = GetCurrentTimeString();
	ofsMainInp << "*Heading" << ENDLINE
		<< "** " << ENDLINE
		<< "** Generated by AnsysExporter v" << verStr << " (Author: wudong <wdhust@gmail.com>)" << ENDLINE
		<< "**       at <" << curTimeStr << ">" << ENDLINE
		<< "** " << ENDLINE
		<< "*Part, name=Part_AnsModel" << ENDLINE
		<< "**" << ENDLINE;

	PrintCurrentLineInfo();

	//
	// Export node data
	//
	auto nodeCount = nodeData.size();
	if (nodeCount > 0)
	{
		std::string setName{ "NSET_ALL_NODE" };
		ofsMainInp << "** NodeCount=" << nodeCount << ENDLINE
			<< "*Node, nset=" << setName << ENDLINE;
		auto fileName = setName + "." + ci.InpFileExt;
		ofsMainInp << "*Include, input=" << ci.OutputSetDataDirName << "\\" << fileName << ENDLINE;

		std::ofstream ofsSetData;
		ofsSetData.open(CPathHelper::PathCombine(ci.OutputMainDir, ci.OutputSetDataDirName, fileName), FileOpenMode);
		for (auto&& node : nodeData)		// for each node
		{
			auto nodeNum = node.first;
			auto nodeCoord = node.second;
			auto x = std::get<0>(nodeCoord);
			auto y = std::get<1>(nodeCoord);
			auto z = std::get<2>(nodeCoord);
			ofsSetData << nodeNum << "," << x << "," << y << "," << z << ENDLINE;	// nodeNum,x,y,z
		}
		ofsSetData.close();

		ofsMainInp << "** " << ENDLINE;
	}

	PrintCurrentLineInfo();

	//
	// Export element data
	//
	int elemCountAll = 0;
	if (!elemData.empty())
	{
		for (auto&& elems : elemData)		// for each elem type
		{
			auto typeNum = elems.first;
			auto& typeName = std::get<0>(elemConvertionMap[typeNum]);
			auto isInfiniteElem = std::get<1>(elemConvertionMap[typeNum]);
			auto elemCount = elems.second.size();

			elemCountAll += elemCount;

			std::string setName{CStringHelper::ToUpper("eset_all_" + typeName)};
			ofsMainInp << "** ElemCount=" << elemCount << ENDLINE
				<< "*Element, type=" << typeName << ", elset=" << setName << ENDLINE;
			auto fileName = setName + "." + ci.InpFileExt;
			ofsMainInp << "*Include, input=" << ci.OutputSetDataDirName << "\\" << fileName << ENDLINE;

			std::ofstream ofsSetData;
			ofsSetData.open(CPathHelper::PathCombine(ci.OutputMainDir, ci.OutputSetDataDirName, fileName), FileOpenMode);
			nodeCount = std::get<0>(internalElemTypes[typeNum]);
			assert((nodeCount > 0) && ((nodeCount % 2) == 0));
			auto nodeCountHalf = nodeCount / 2;
			for (auto&& elem : elems.second)	// for each element
			{
				auto vecSize = elem.size();
				assert(nodeCount == (elem.size() - 1));
				ofsSetData << elem[0];			// elemNum
				if (!isInfiniteElem)
				{
					for (size_t i = 1; i < vecSize; i++)
					{
						ofsSetData << "," << elem[i];	// ,n1,n2,n3,n4 ......
					}
				}
				else
				{
					auto vecIndex = [](size_t vecSize, size_t index)->int { return ((index - 1) % (vecSize - 1)) + 1; };
					size_t firstHalfBase = 1;
					for (; firstHalfBase < vecSize; firstHalfBase++)
					{
						int maxOfFirstHalf = elem[vecIndex(vecSize, firstHalfBase)];
						int minOfSecondHalf = elem[vecIndex(vecSize, firstHalfBase + nodeCountHalf)];
						for (size_t i = 1; i < nodeCountHalf; i++)
						{
							auto v = elem[vecIndex(vecSize, firstHalfBase + i)];
							maxOfFirstHalf = max(maxOfFirstHalf, v);

							v = elem[vecIndex(vecSize, firstHalfBase + nodeCountHalf + i)];
							minOfSecondHalf = min(minOfSecondHalf, v);
						}
						if (maxOfFirstHalf < minOfSecondHalf)
						{
							break;
						}
					}
					for (size_t i = 0; i < nodeCount; i++)
					{
						ofsSetData << "," << elem[vecIndex(vecSize, firstHalfBase + i)];	// ,n1,n2,n3,n4 ......
					}
				}
				ofsSetData << ENDLINE;
			}
			ofsSetData.close();

			ofsMainInp << "** " << ENDLINE;
		}
	}

	PrintCurrentLineInfo();

	//
	// Export group data
	//
	std::map<std::string, int> exportGroups;
	if (!groupData.empty())
	{
		static std::map<GroupType, std::tuple<std::string, std::string, std::string, std::string>> groupMap
		{
			std::make_pair(GroupType::Node,    std::make_tuple("NodeCount", "NSET", "Nset", "nset")),
			std::make_pair(GroupType::Element, std::make_tuple("ElemCount", "ESET", "Elset", "elset"))
		};
		for (auto&& group : groupData)		// for each group
		{
			auto groupName = CStringHelper::ToUpper(group.first);

			CAnsysHelper::PrintDebugString("Start exporting group : %s", groupName.c_str());

			auto groupType = std::get<0>(group.second);
			auto& groupMembers = std::get<1>(group.second);

			auto& countPrefix = std::get<0>(groupMap[groupType]);
			auto& setNamePrefix = std::get<1>(groupMap[groupType]);
			auto& setCmd = std::get<2>(groupMap[groupType]);
			auto& setKeyword = std::get<3>(groupMap[groupType]);

			std::string setName = setNamePrefix + "_" + groupName;
			ofsMainInp << "** " << countPrefix << "=" << groupMembers.size() << ENDLINE
				<< "*" << setCmd <<", "<< setKeyword << "=" << setName << ENDLINE;
			auto fileName = setName + "." + ci.InpFileExt;
			ofsMainInp << "*Include, input=" << ci.OutputSetDataDirName << "\\" << fileName << ENDLINE;

			std::ofstream ofsSetData;
			ofsSetData.open(CPathHelper::PathCombine(ci.OutputMainDir, ci.OutputSetDataDirName, fileName), FileOpenMode);
			for (size_t i = 0; i < groupMembers.size(); i++)
			{
				const int NumCountEachLine = 16;
				ofsSetData << groupMembers[i] << (((i + 1) % NumCountEachLine != 0) ? ',' : ENDLINE);
			}
			ofsSetData.close();

			ofsMainInp << "** " << ENDLINE;

			exportGroups[groupName] = groupMembers.size();

			//
			CAnsysHelper::PrintDebugString("End exporting group : %s", groupName.c_str());
		}
	}

	//
	ofsMainInp << "** " << ENDLINE
		<< "*End Part" << ENDLINE;
	ofsMainInp.close();

	CAnsysHelper::PrintDebugString("End exporting actions");

	//////////////////////////////////////////////////////////////////////////
	// Print output result
	//
	auto endTime = ATL::CTime::GetCurrentTime();
	auto dt = endTime - startTime;

	std::stringstream ss;
	ss << ENDLINE << ENDLINE
		<< "****************** Output result ******************" << ENDLINE
		<< "All selected elements have been converted." << ENDLINE
		<< "Working directory : " << ci.CurWorkDir << ENDLINE
		<< "Output directory  : " << mainFilePath << ENDLINE
		<< "Node count : " << nodeCount << ENDLINE
		<< "Elem count : " << elemCountAll << ENDLINE;
	if (!elemData.empty())
	{
		for (auto&& elems : elemData)		// for each elem type
		{
			auto typeNum = elems.first;

			CAnsysHelper::PrintDebugString("Print elem count info, typeNum : %d", typeNum);

			auto& destTypeName = std::get<0>(elemConvertionMap[typeNum]);
			auto elemCount = elems.second.size();
			auto& srcTypeName = std::get<1>(internalElemTypes[typeNum]);
			ss << "\t" << srcTypeName << " (" << destTypeName << ") : " << elemCount << ENDLINE;
		}
	}
	if (!exportGroups.empty())
	{
		ss << "Groups:" << ENDLINE;
		for (auto&& group : exportGroups)
		{
			CAnsysHelper::PrintDebugString("Print group info, GroupName : %s", group.first.c_str());

			ss << "\t" << group.first << " : " << group.second << ENDLINE;
		}
	}

	CAnsysHelper::PrintDebugString("Print elem and group info");

	CAnsysHelper::PrintString("%s", ss.str().c_str());
	CAnsysHelper::PrintString("Conversion time = %02d:%02d:%02d\n", dt.GetHours(), dt.GetMinutes(), dt.GetSeconds());

	//
	PrintAppVersionInfo();

	return 0;
}
#pragma endregion

#pragma region command to export node info used by dynamic analysis in abaqus
class CExniCmdInfo
{
public:
	CExniCmdInfo(char* uecmd)
	{
		//
		// Tokenize command line
		//
		std::string cmdLine{ uecmd };
		auto argCount = std::count_if(cmdLine.cbegin(), cmdLine.cend(), [](char c) { return c == ','; });
		if (argCount<ArgCount)
		{
			cmdLine += std::string((ArgCount - argCount), ',');
		}

		auto tokens = CStringHelper::SplitString(cmdLine, ',');
		if (tokens.size() <= 1)
		{
			return;
		}
		std::vector<std::string> arguments;
		for (size_t i = 1; i < tokens.size(); i++)
		{
			arguments.push_back(CStringHelper::Trim(tokens[i], std::string(" \t\"\'")));
		}

		//
		// Get output file name
		//
		this->OutputFileName = arguments[0];

		//
		// Get current work directory
		//
		CAnsysHelper::RunAnsysCommand("/inquire,workdir0_,directory");
		this->CurWorkDir = CPathHelper::PathNormalize(CAnsysHelper::GetStringVar("workdir0_(1)"));

		//
		// Get main output directory
		//
		this->OutputMainDir = this->CurWorkDir;
		std::string outputMainDir{ arguments[1] };
		std::transform(arguments[1].cbegin(), arguments[1].cend(), outputMainDir.begin(), [](char c) { return c == '/' ? '\\' : c; });
		outputMainDir = CStringHelper::Trim(outputMainDir, std::string{ " \t\\" });
		if (outputMainDir.length()>0)
		{
			if (::PathIsRelativeA(outputMainDir.c_str()))
			{
				this->OutputMainDir = CPathHelper::PathNormalize(CPathHelper::PathCombine(this->CurWorkDir, outputMainDir));
			}
			else
			{
				this->OutputMainDir = CPathHelper::PathNormalize(outputMainDir);
			}
			if (CDirectoryHelper::IsDirectoryExists(this->OutputMainDir))
			{
				CDirectoryHelper::CreateDirectory(this->OutputMainDir);
			}
		}
	}

private:
	CExniCmdInfo() {}

public:
	std::string OutputFileName;
	std::string CurWorkDir;
	std::string OutputMainDir;
	const int ArgCount{ 2 };
};

//
// The main entrance function
//
extern "C" __declspec(dllexport)
int __cdecl ExportNodeInfo(char* uecmd)
{
	PrintAppVersionInfo();

	CExniCmdInfo ci(uecmd);
	if (ci.OutputMainDir.empty())
	{
		CAnsysHelper::PrintString(R"(
Export ANSYS node information: nodeNum, x, y, z, nodeArea

Command Format :
	~exni,fileName [,outputPath]
Sample :
	~exni,'nodeInfo.txt','D:\My\OutputDir\'
		)");
		PrintAppVersionInfo();

		return 0;
	}

	//
	// Get node count
	//
	CAnsysHelper::RunAnsysCommand(R"(
		*get,node_count_,node,,count
		*dim,ni_data_,array,node_count_,5	! (nodeNum,x,y,z,nodeArea)
		nn_ = 0
		*do,i,1,node_count_
			nn_ = ndnext(nn_)
			ni_data_(i,1) = nn_
			ni_data_(i,2) = nx(nn_)
			ni_data_(i,3) = ny(nn_)
			ni_data_(i,4) = nz(nn_)
			ni_data_(i,5) = arnode(nn_)
		*enddo
		nn_ = 
	)");

	auto nodeCount = CAnsysHelper::GetIntVar("node_count_");
	if (nodeCount > 0)
	{
		auto rawNodeInfoData = CAnsysHelper::GetDoubleArray("ni_data_", nodeCount * 5);

		std::ofstream ofsNodeInfo;
		std::string nodeInfoFilePath{ CPathHelper::PathCombine(ci.OutputMainDir, ci.OutputFileName) };
		CAnsysHelper::PrintDebugString("nodeInfoFilePath = %s", nodeInfoFilePath.c_str());
		const auto FileOpenMode = std::ios_base::out | std::ios_base::trunc;
		ofsNodeInfo.open(nodeInfoFilePath, FileOpenMode);
		std::array<int, 4> base{ nodeCount, nodeCount * 2, nodeCount * 3, nodeCount * 4 };
		for (int i = 0; i < nodeCount; i++)
		{
			auto nodeNum = static_cast<int>(rawNodeInfoData[i]);
			auto x = rawNodeInfoData[base[0] + i];
			auto y = rawNodeInfoData[base[1] + i];
			auto z = rawNodeInfoData[base[2] + i];
			auto nodeArea = rawNodeInfoData[base[3] + i];
			ofsNodeInfo << nodeNum << ", " << x << ", " << y << ", " << z << ", " << nodeArea << ENDLINE;
		}
		ofsNodeInfo.close();
	}

	CAnsysHelper::DeleteArrayVar("ni_data_");
	CAnsysHelper::DeleteOrdVar("node_count_");

	PrintAppVersionInfo();

	return 0;
}
#pragma endregion 

